/*
Copyright 2008 Ian Thomas

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.wildwinter.anthx;

import org.apache.tools.ant.Task;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.ExecTask;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.Commandline;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;

import java.security.MessageDigest;
import java.math.BigInteger;
import java.io.*;
import java.util.regex.*;
import java.util.*;
import java.net.URLDecoder;

/** Embed haxe script directly into Ant */
public class HaxeEmbed extends Task 
{
	private Script _script=null;
	private String _text="";
    protected Vector<Lib> _libs = new Vector<Lib>();
	protected Path _classPath;
		
	public HaxeEmbed()
	{
	}
    
    public void execute() 
    {
	    if (_script!=null)
			_text+=_script.getText();
		if (_text.trim()=="")
			throw new BuildException("No script contained in antHX");
			
		String tempRoot=System.getenv("TEMP");
		String hashKey=getProject().getBaseDir().getAbsolutePath()+getProject().getName();
		String hash;
		
		File propsFile=getProject().resolveFile(tempRoot+"/anthx/anthx.properties");

		try
		{
			MessageDigest m=MessageDigest.getInstance("MD5");
			m.update(hashKey.getBytes(),0,hashKey.length());
			hash=new BigInteger(1,m.digest()).toString(16);
		}
		catch (Exception e)
		{
			throw new BuildException("Internal error:"+e);
		}
		
		File tempFolder=getProject().resolveFile(tempRoot+"/anthx/"+hash);
		tempFolder.mkdirs();
    
		File mainFile=new File(tempFolder,"App.hx");
		
		String outputCode=createHaxeCode();
		
		try
		{
		    FileWriter fstream = new FileWriter(mainFile);
		    BufferedWriter out = new BufferedWriter(fstream);
		    out.write(outputCode);
		    out.close();
	    }
	    catch (Exception e)
	    {
			throw new BuildException("Error creating haxeEmbed script file:"+e);
    	}
    	
	    // Compile the file
	    ExecTask haxe=HaxeEnv.createHaxeTask(getProject());
	    haxe.setDir(tempFolder);
	    haxe.createArg().setLine("-lib anthx");

		// Add libs
		for (Enumeration e = _libs.elements(); e.hasMoreElements();)
		{
            Lib lib = (Lib) e.nextElement();
            haxe.createArg().setLine("-lib "+lib.format());
        }
        
        // Classpath
        if (_classPath!=null)
        {
	        String paths[]=_classPath.list();
	        for (int i=0;i<paths.length;i++)
	        {
		        haxe.createArg().setLine("-cp "+paths[i]);
	        }
        }
        
        haxe.createArg().setLine("-main App");
  	    haxe.createArg().setLine("-neko App.n");
        	   		
	    haxe.execute();

 	    // Get rid of any properties output by the last script
	   	if (propsFile.exists())
	   		propsFile.delete();

	    // Run the file
	    File nekoFile=new File(tempFolder,"App.n");
	    if (!nekoFile.exists())
	    	throw new BuildException("Failed to compile haxeEmbed code.",getLocation());
	    ExecTask neko=HaxeEnv.createNekoTask(getProject());
	    neko.setDir(getProject().getBaseDir());
		for (Enumeration e = _libs.elements(); e.hasMoreElements();)
		{
            Lib lib = (Lib) e.nextElement();
            HaxeEnv.addNekoLibPath(neko,lib.format());
        }
	    neko.createArg().setLine(nekoFile.getAbsolutePath());
	    neko.execute();
	    
	    // Read any properties
	    Properties props=new Properties();
	    if (propsFile.exists())
	    {
		    try
		    {
			    FileInputStream fstream=new FileInputStream(propsFile);
			    BufferedInputStream in=new BufferedInputStream(fstream);
			    props.load(in);
			    in.close();
		    }
		    catch (Exception e)
		    {
			    throw new BuildException("Error retrieving properties set by haXe:"+e);
		    }
		    
		    try
		    {
			    // Iterate through decoding them
			    for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();)
			    {
		       		String propName=e.nextElement().toString();
		       		String value=URLDecoder.decode(props.getProperty(propName),"UTF-8");
		       		getProject().setProperty(propName,value);
		   		}
			}
			catch (Exception e)
			{
				throw new BuildException("Error decoding property set by haXe:"+e);
			}
		}
    }
    
    private String createHaxeCode()
    {
	    String script=_text;
	    String out="";
	     
	    {
			Pattern pat=Pattern.compile("\\$\\{[^\\s\\}]*}");
		    Matcher m=pat.matcher(script);
		    StringBuffer sb = new StringBuffer();
		    while (m.find())
		    {
			    String prop=m.group(0);
			    String rep=getProject().replaceProperties(prop);
				rep=rep.replace("\\","\\\\\\\\");

				m.appendReplacement(sb,rep);
		    }
		    m.appendTail(sb);
		    script=sb.toString();
	    }
	    
	   {
		    // Extract any import statements.
		    Pattern pat=Pattern.compile("import\\s([\\w\\.]*);");
		    Matcher m=pat.matcher(script);
		    StringBuffer sb = new StringBuffer();
		    while (m.find())
		    {
			    out+=m.group(0);
			    m.appendReplacement(sb,"");
		    }
		    m.appendTail(sb);
		    script=sb.toString();
	    }
	    
	    out+="import net.wildwinter.anthx.AntHX;";
	    out+="class App	extends AntHX {";
	    out+="static function main(){ var app:App=new App(); app.run();}";
	    out+="override public function script():Void{";
	    out+=script;
	    out+="}";
	    out+="}";
	    
	    return out;
    }
    
    public void addText(String value)
    {
	   _text+=value;
    }
    /******************* Attributes ****************************/
	/** Classpath - mimic java task functionality */
	public void setClasspath(Path s) 
	{
        createClasspath().append(s);
    }
    
	/** Classpath - mimic java task functionality */
	public void setClasspathref(Path s) 
	{
        createClasspath().append(s);
    }
    
    /** Classpathref - mimic java task functionality */
    public void setClasspathRef(Reference r) 
    {
        createClasspath().setRefid(r);
    }
    
    /******************* Elements ****************************/
    public Lib createLib() 
    {
        Lib lib = new Lib();
        _libs.add(lib);
        return lib;
    }
    
    public Path createClasspath() 
    {
	    if (_classPath==null)
	    	_classPath=new Path(getProject());
	    return _classPath;
    }
    
    public Script createScript() 
    {
	    if (_script!=null)
	    	throw new BuildException("Can't have more than one script element in antHX");
        _script = new Script();
        return _script;
    }
    
    /***********************************************************/
    public class Script
    {
	    private String _text;

        public void addText(String value)
        { 
	        _text = value;
	    }
	    
        public String getText() 
        { 
	        return _text; 
	    }
    }
   
    public class Lib
    {
	    private String _name;
	    private String _version;

        public void setName(String value)
        { 
	        _name = value;
	    }
	    
        public void setVersion(String value)
        { 
	        _version = value;
	    }
	    
	    public String format() throws BuildException
	    {
		    if (_name==null)
		    	throw new BuildException("No name specified for haxe lib.");
		    if (_version!=null)
		    	return _name+":"+_version;
		    return _name;
	    }
    }
}